

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

// 食物类，用于表示游戏中的食物，包含食物的位置、颜色和积分信息
class Food {
    // 食物的位置，使用 Point 类表示二维坐标
    Point position;
    // 食物的颜色
    Color color;
    // 食物被蛇吃掉后能获得的积分
    int score;

    // 构造函数，用于初始化食物的位置、颜色和积分
    public Food(Point position, Color color, int score) {
        this.position = position;
        this.color = color;
        this.score = score;
    }
}

// 食物生成线程类，实现了 Runnable 接口，用于在单独的线程中生成食物
class FoodGenerator implements Runnable {
    // 持有 GamePanel 对象的引用，用于调用游戏面板的方法
    private final GamePanel gamePanel;
    // 该线程生成的食物的颜色
    private final Color color;
    // 该线程生成的食物对应的积分
    private final int score;
    // 食物生成的时间间隔，单位为毫秒
    private final long interval;

    // 构造函数，初始化食物生成线程所需的参数
    public FoodGenerator(GamePanel gamePanel, Color color, int score, long interval) {
        this.gamePanel = gamePanel;
        this.color = color;
        this.score = score;
        this.interval = interval;
    }

    // 实现 Runnable 接口的 run 方法，线程启动后会执行此方法
    @Override
    public void run() {
        // 当游戏处于运行状态时，不断循环执行食物生成逻辑
        while (gamePanel.isRunning()) {
            // 对游戏面板的锁对象进行同步，确保线程安全
            synchronized (gamePanel.getLock()) {
                // 检查当前游戏中的食物数量是否达到最大允许数量
                while (gamePanel.getFoods().size() >= gamePanel.getMaxFoods()) {
                    try {
                        // 如果达到最大数量，线程进入等待状态，释放锁
                        gamePanel.getLock().wait();
                    } catch (InterruptedException e) {
                        // 处理线程中断异常，重新设置中断状态
                        Thread.currentThread().interrupt();
                    }
                }
                // 当食物数量未达到最大时，调用游戏面板的 spawnFood 方法生成食物
                gamePanel.spawnFood(color, score);
                // 唤醒所有在该锁对象上等待的线程
                gamePanel.getLock().notifyAll();
            }
            try {
                // 线程休眠指定的时间间隔，控制食物生成的频率
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                // 处理线程中断异常，重新设置中断状态
                Thread.currentThread().interrupt();
            }
        }
    }
}

// 游戏面板类，继承自 JPanel 并实现 ActionListener 接口，用于绘制游戏界面和处理游戏逻辑
class GamePanel extends JPanel implements ActionListener {
    // 游戏面板的宽度，单位为像素
    private static final int WIDTH = 600;
    // 游戏面板的高度，单位为像素
    private static final int HEIGHT = 600;
    // 游戏中每个网格单元的大小，单位为像素
    private static final int UNIT_SIZE = 20;
    // 游戏中允许存在的最大食物数量
    private static final int MAX_FOODS = 5;
    // 红色食物被蛇吃掉后能获得的积分
    private static final int RED_FOOD_SCORE = 10;
    // 黄色食物被蛇吃掉后能获得的积分
    private static final int YELLOW_FOOD_SCORE = 5;
    // 红色食物生成的时间间隔，单位为毫秒
    private static final long RED_FOOD_INTERVAL = 3000;
    // 黄色食物生成的时间间隔，单位为毫秒
    private static final long YELLOW_FOOD_INTERVAL = 2000;

    // 存储蛇身体的所有坐标点
    private final List<Point> snakeBody;
    // 存储游戏中所有的食物对象
    private final List<Food> foods;
    // 蛇当前的移动方向，'U' 表示向上，'D' 表示向下，'L' 表示向左，'R' 表示向右
    private char direction = 'R';
    // 游戏是否正在运行的标志
    private boolean running = false;
    // 用于定时触发游戏更新的定时器
    private Timer timer;
    // 用于生成随机数，用于随机生成食物的位置
    private final Random random;
    // 定时器的延迟时间，控制蛇的移动速度
    private int delay;
    // 玩家当前的游戏得分
    private int score;
    // 用于线程同步的锁对象
    private final Object lock = new Object();

    // 游戏面板的构造函数，初始化游戏面板的基本属性
    public GamePanel() {
        random = new Random();
        snakeBody = new ArrayList<>();
        foods = new ArrayList<>();
        // 设置游戏面板的首选大小
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        // 设置游戏面板的背景颜色为黑色
        setBackground(Color.BLACK);
        // 设置游戏面板可以获取焦点，以便接收键盘事件
        setFocusable(true);

        // 为游戏面板添加键盘事件监听器
        addKeyListener(new MyKeyAdapter());
    }

    // 开始游戏的方法，初始化游戏状态并启动定时器和食物生成线程
    public void startGame(int delay) {
        this.delay = delay;
        // 清空蛇的身体坐标
        snakeBody.clear();
        // 初始化蛇的初始位置
        snakeBody.add(new Point(200, 200));
        // 清空游戏中的食物列表
        foods.clear();
        // 初始化玩家得分
        score = 0;
        // 设置游戏状态为运行中
        running = true;
        // 初始化蛇的移动方向为向右
        direction = 'R';
        // 创建定时器，设置延迟时间和事件监听器
        timer = new Timer(delay, this);
        // 启动定时器
        timer.start();
        // 请求游戏面板获取焦点
        requestFocusInWindow();

        // 启动红色食物生成线程
        new Thread(new FoodGenerator(this, Color.RED, RED_FOOD_SCORE, RED_FOOD_INTERVAL)).start();
        // 启动黄色食物生成线程
        new Thread(new FoodGenerator(this, Color.YELLOW, YELLOW_FOOD_SCORE, YELLOW_FOOD_INTERVAL)).start();
    }

    // 重写 JPanel 的 paintComponent 方法，用于绘制游戏界面
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // 调用 draw 方法绘制游戏元素
        draw(g);
    }

    // 绘制游戏元素的方法，包括食物、蛇、网格线和得分
    private void draw(Graphics g) {
        if (running) {
            // 绘制所有食物
            for (Food food : foods) {
                g.setColor(food.color);
                g.fillRect(food.position.x, food.position.y, UNIT_SIZE, UNIT_SIZE);
            }

            // 绘制蛇的身体
            for (Point point : snakeBody) {
                g.setColor(Color.GREEN);
                g.fillRect(point.x, point.y, UNIT_SIZE, UNIT_SIZE);
            }

            // 绘制网格线
            g.setColor(Color.GRAY);
            for (int i = 0; i < HEIGHT / UNIT_SIZE; i++) {
                g.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, HEIGHT);
                g.drawLine(0, i * UNIT_SIZE, WIDTH, i * UNIT_SIZE);
            }

            // 绘制玩家得分
            g.setColor(Color.WHITE);
            g.setFont(new Font("Arial", Font.PLAIN, 24));
            g.drawString("分数: " + score, 20, 30);
        }
    }

    // 生成食物的方法，随机生成食物的位置并添加到食物列表中
    public void spawnFood(Color color, int score) {
        // 随机生成食物的 x 坐标
        int x = random.nextInt((int) (WIDTH / UNIT_SIZE)) * UNIT_SIZE;
        // 随机生成食物的 y 坐标
        int y = random.nextInt((int) (HEIGHT / UNIT_SIZE)) * UNIT_SIZE;
        // 创建食物的位置对象
        Point position = new Point(x, y);
        // 检查食物的位置是否在蛇的身体上
        if (!snakeBody.contains(position)) {
            // 如果不在蛇的身体上，创建食物对象并添加到食物列表中
            foods.add(new Food(position, color, score));
        }
    }

    // 蛇移动的方法，更新蛇的位置并检查是否吃到食物
    private void move() {
        // 获取蛇头的坐标
        Point head = new Point(snakeBody.get(0));
        // 根据蛇的移动方向更新蛇头的坐标
        switch (direction) {
            case 'U':
                head.y -= UNIT_SIZE;
                break;
            case 'D':
                head.y += UNIT_SIZE;
                break;
            case 'L':
                head.x -= UNIT_SIZE;
                break;
            case 'R':
                head.x += UNIT_SIZE;
                break;
        }
        // 将新的蛇头坐标添加到蛇的身体列表中
        snakeBody.add(0, head);

        // 检查蛇是否吃到食物
        for (int i = 0; i < foods.size(); i++) {
            Food food = foods.get(i);
            if (head.equals(food.position)) {
                // 对锁对象进行同步，确保线程安全
                synchronized (lock) {
                    // 移除被吃掉的食物
                    foods.remove(i);
                    // 增加玩家得分
                    score += food.score;
                    // 唤醒所有在该锁对象上等待的线程
                    lock.notifyAll();
                }
                break;
            }
        }

        // 如果蛇的身体长度超过得分对应的长度，移除蛇尾
        if (snakeBody.size() > score / 10 + 1) {
            snakeBody.remove(snakeBody.size() - 1);
        }
    }

    // 检查蛇是否发生碰撞的方法，包括撞到边界和撞到自己
    private void checkCollisions() {
        // 获取蛇头的坐标
        Point head = snakeBody.get(0);

        // 检查蛇是否撞到边界
        if (head.x < 0 || head.x >= WIDTH || head.y < 0 || head.y >= HEIGHT) {
            // 如果撞到边界，设置游戏状态为结束
            running = false;
        }

        // 检查蛇是否撞到自己
        for (int i = 1; i < snakeBody.size(); i++) {
            if (head.equals(snakeBody.get(i))) {
                // 如果撞到自己，设置游戏状态为结束
                running = false;
            }
        }

        // 当游戏结束时，停止定时器，显示游戏结束对话框并切换到开始界面
        if (!running) {
            timer.stop();
            JOptionPane.showMessageDialog(this, "游戏结束！你的分数是: " + score);
            ((CardLayout) getParent().getLayout()).show(getParent(), "start");
        }
    }

    // 实现 ActionListener 接口的 actionPerformed 方法，定时器触发时会调用此方法
    @Override
    public void actionPerformed(ActionEvent e) {
        if (running) {
            // 调用 move 方法更新蛇的位置
            move();
            // 调用 checkCollisions 方法检查蛇是否发生碰撞
            checkCollisions();
        }
        // 重绘游戏面板，更新游戏界面
        repaint();
    }

    // 获取游戏是否正在运行的状态
    public boolean isRunning() {
        return running;
    }

    // 获取游戏中所有的食物对象列表
    public List<Food> getFoods() {
        return foods;
    }

    // 获取游戏中允许存在的最大食物数量
    public int getMaxFoods() {
        return MAX_FOODS;
    }

    // 获取用于线程同步的锁对象
    public Object getLock() {
        return lock;
    }

    // 内部类，用于处理键盘事件，控制蛇的移动方向
    private class MyKeyAdapter extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_A:
                    if (direction != 'R') {
                        direction = 'L';
                    }
                    break;
                case KeyEvent.VK_D:
                    if (direction != 'L') {
                        direction = 'R';
                    }
                    break;
                case KeyEvent.VK_W:
                    if (direction != 'D') {
                        direction = 'U';
                    }
                    break;
                case KeyEvent.VK_S:
                    if (direction != 'U') {
                        direction = 'D';
                    }
                    break;
            }
        }
    }
}

// 开始界面类，继承自 JPanel，用于显示游戏开始界面和难度选择
class StartPanel extends JPanel {
    // 简单难度下定时器的延迟时间，单位为毫秒
    private static final int EASY_DELAY = 200;
    // 中等难度下定时器的延迟时间，单位为毫秒
    private static final int MEDIUM_DELAY = 150;
    // 困难难度下定时器的延迟时间，单位为毫秒
    private static final int HARD_DELAY = 100;

    // 开始界面的构造函数，初始化界面元素和事件监听器
    public StartPanel(GamePanel gamePanel) {
        // 使用 GridBagLayout 布局管理器
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        // 设置组件之间的间距
        gbc.insets = new Insets(10, 10, 10, 10);
        gbc.gridx = 0;
        gbc.gridy = 0;

        // 创建游戏标题标签
        JLabel titleLabel = new JLabel("贪吃蛇游戏");
        titleLabel.setFont(new Font("宋体", Font.BOLD, 36));
        // 将标题标签添加到界面中
        add(titleLabel, gbc);

        gbc.gridy = 1;
        // 创建难度选择标签
        JLabel difficultyLabel = new JLabel("选择难度:");
        difficultyLabel.setFont(new Font("宋体", Font.PLAIN, 24));
        // 将难度选择标签添加到界面中
        add(difficultyLabel, gbc);

        gbc.gridy = 2;
        // 创建按钮组，用于管理难度选择的单选按钮
        ButtonGroup difficultyGroup = new ButtonGroup();

        // 创建简单难度的单选按钮
        JRadioButton easyButton = new JRadioButton("简单");
        easyButton.setFont(new Font("宋体", Font.PLAIN, 20));
        // 默认选择简单难度
        easyButton.setSelected(true);
        // 将简单难度的单选按钮添加到按钮组中
        difficultyGroup.add(easyButton);
        // 将简单难度的单选按钮添加到界面中
        add(easyButton, gbc);

        gbc.gridy = 3;
        // 创建中等难度的单选按钮
        JRadioButton mediumButton = new JRadioButton("中等");
        mediumButton.setFont(new Font("宋体", Font.PLAIN, 20));
        // 将中等难度的单选按钮添加到按钮组中
        difficultyGroup.add(mediumButton);
        // 将中等难度的单选按钮添加到界面中
        add(mediumButton, gbc);

        gbc.gridy = 4;
        // 创建困难难度的单选按钮
        JRadioButton hardButton = new JRadioButton("困难");
        hardButton.setFont(new Font("宋体", Font.PLAIN, 20));
        // 将困难难度的单选按钮添加到按钮组中
        difficultyGroup.add(hardButton);
        // 将困难难度的单选按钮添加到界面中
        add(hardButton, gbc);

        gbc.gridy = 5;
        // 创建开始游戏按钮
        JButton startButton = new JButton("开始");
        startButton.setFont(new Font("宋体", Font.PLAIN, 24));
        // 为开始游戏按钮添加点击事件监听器
        startButton.addActionListener(e -> {
            int delay;
            if (easyButton.isSelected()) {
                delay = EASY_DELAY;
            } else if (mediumButton.isSelected()) {
                delay = MEDIUM_DELAY;
            } else {
                delay = HARD_DELAY;
            }
            // 切换到游戏界面并启动游戏
            ((CardLayout) getParent().getLayout()).show(getParent(), "game");
            gamePanel.startGame(delay);
        });
        // 将开始游戏按钮添加到界面中
        add(startButton, gbc);

        gbc.gridy = 6;
        // 创建退出游戏按钮
        JButton exitButton = new JButton("退出");
        exitButton.setFont(new Font("宋体", Font.PLAIN, 24));
        // 为退出游戏按钮添加点击事件监听器，点击后退出程序
        exitButton.addActionListener(e -> System.exit(0));
        // 将退出游戏按钮添加到界面中
        add(exitButton, gbc);
    }
}

// 主类，程序的入口点
public class SnakeGame {
    public static void main(String[] args) {
        // 在事件调度线程中创建和显示游戏窗口
        EventQueue.invokeLater(() -> {
            // 创建游戏窗口
            JFrame frame = new JFrame("Snake Game");
            // 设置窗口关闭时的操作，即退出程序
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 将窗口居中显示
            frame.setLocationRelativeTo(null);

            // 创建游戏面板对象java SnakeGame

            GamePanel gamePanel = new GamePanel();
            // 创建开始界面对象
            StartPanel startPanel = new StartPanel(gamePanel);

            // 创建一个使用 CardLayout 布局的面板，用于切换开始界面和游戏界面
            JPanel cardPanel = new JPanel(new CardLayout());
            // 将开始界面添加到面板中
            cardPanel.add(startPanel, "start");
            // 将游戏面板添加到面板中
            cardPanel.add(gamePanel, "game");

            // 将包含开始界面和游戏界面的面板添加到游戏窗口中
            frame.add(cardPanel);
            // 调整窗口大小以适应组件
            frame.pack();
            // 显示游戏窗口
            frame.setVisible(true);
        });
    }
}
